/*
 * camer.c
 *
 *  Created on: 2014-8-16
 *      Author: liu
 */
#include "camer.h"

void write_data_to_file(unsigned char *ptr, int length, char *filename) {
	printf("write to file %s start!\n", filename);
	FILE* fd = fopen(filename, "w");
	if (NULL == fd) {
		printf("open %s file failed!\n", filename);
		return;
	}
	printf("write data to file :%s \nlenght: %d\n", filename, length);
	fwrite("P6\n320 240\n255\n", sizeof("P6\n320 240\n255\n"), 1, fd);
	fwrite(ptr, length, 1, fd);
	fclose(fd);
}

Camera* calloc_camera() {
	Camera* tmp = (Camera*) calloc(1, sizeof(Camera));
	if (!tmp) {
		printf("calloc the Camera struct buf failed!\n");
		return NULL;
	}

	return tmp;
}

void free_camera(Camera* ptr) {
	if (!ptr) {
		printf("free the Camera struct buf failed!\n");
	} else {
		free(ptr);
	}
}

//打开摄像头设备
int open_camer_device() {
	int fd;

	if ((fd = open("/dev/video0", O_RDWR | O_NONBLOCK)) < 0) {
		perror("Fail to open");
		exit(EXIT_FAILURE);
	}

	return fd;
}

int init_mmap(Camera *args) {
	int i = 0;
	struct v4l2_requestbuffers reqbuf;

	bzero(&reqbuf, sizeof(reqbuf));
	reqbuf.count = 8;
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbuf.memory = V4L2_MEMORY_MMAP;

	//申请视频缓冲区(这个缓冲区位于内核空间，需要通过mmap映射)
	//这一步操作可能会修改reqbuf.count的值，修改为实际成功申请缓冲区个数
	if (-1 == ioctl(args->camera_fd, VIDIOC_REQBUFS, &reqbuf)) {
		perror("Fail to ioctl 'VIDIOC_REQBUFS'");
		exit(EXIT_FAILURE);
	}
	args->n_buffer = reqbuf.count;
	printf("n_buffer = %d\n", args->n_buffer);

	args->buf = (Frame_buf*) calloc(reqbuf.count, sizeof(Frame_buf));
	if (!(args->buf)) {
		printf("calloc the Frame_buf struct buf failed!\n");
		return -1;
	}

	//将内核缓冲区映射到用户进程空间
	for (i = 0; i < reqbuf.count; i++) {
		struct v4l2_buffer buf;

		bzero(&buf, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;
		//查询申请到内核缓冲区的信息
		if (-1 == ioctl(args->camera_fd, VIDIOC_QUERYBUF, &buf)) {
			perror("Fail to ioctl : VIDIOC_QUERYBUF");
			exit(EXIT_FAILURE);
		}

		args->buf[i].length = buf.length;
		args->buf[i].start = mmap(
		NULL,/*start anywhere*/
		buf.length,
		PROT_READ | PROT_WRITE,
		MAP_SHARED, args->camera_fd, buf.m.offset);
		if (MAP_FAILED == args->buf[i].start) {
			perror("Fail to mmap");
			exit(EXIT_FAILURE);
		}
	}

	return 0;
}

//初始化视频设备
int init_camer_device(Camera *args) {
	struct v4l2_fmtdesc fmt;
	struct v4l2_capability cap;
	struct v4l2_format stream_fmt;
	struct v4l2_streamparm stream_parm;
	int ret;

	//当前视频设备支持的视频格式
	memset(&fmt, 0, sizeof(fmt));
	fmt.index = 0;
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	while ((ret = ioctl(args->camera_fd, VIDIOC_ENUM_FMT, &fmt)) == 0) {
		fmt.index++;

		printf("{pixelformat = %c%c%c%c},description = '%s'\n",
				fmt.pixelformat & 0xff, (fmt.pixelformat >> 8) & 0xff,
				(fmt.pixelformat >> 16) & 0xff, (fmt.pixelformat >> 24) & 0xff,
				fmt.description);
	}

	//查询视频设备驱动的功能
	ret = ioctl(args->camera_fd, VIDIOC_QUERYCAP, &cap);
	if (ret < 0) {
		perror("FAIL to ioctl VIDIOC_QUERYCAP");
		exit(EXIT_FAILURE);
	}

	//判断是否是一个视频捕捉设备
	if (!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE)) {
		printf("The Current device is not a video capture device\n");
		exit(EXIT_FAILURE);

	}

	//判断是否支持视频流形式
	if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
		printf("The Current device does not support streaming i/o\n");
		exit(EXIT_FAILURE);
	}

	//设置摄像头采集数据格式，如设置采集数据的
	//长,宽，图像格式(JPEG,YUYV,MJPEG等格式)
	stream_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	stream_fmt.fmt.pix.width = args->width;
	stream_fmt.fmt.pix.height = args->height;
	stream_fmt.fmt.pix.pixelformat = args->type;
	stream_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
	if (-1 == ioctl(args->camera_fd, VIDIOC_S_FMT, &stream_fmt)) {
		perror("Fail to ioctl");
		exit(EXIT_FAILURE);
	}

	//控制帧率
	memset(&stream_parm, 0, sizeof(stream_parm));
	stream_parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	stream_parm.parm.capture.timeperframe.numerator = 1;
	stream_parm.parm.capture.timeperframe.denominator = 10;
	if (-1 == ioctl(args->camera_fd, VIDIOC_S_PARM, &stream_parm)) {
		printf("VIDIOC_S_PARM failed!\n");
		exit(-1);
	}
	printf("numerator: %d, denominator: %d\n",
			stream_parm.parm.capture.timeperframe.numerator,
			stream_parm.parm.capture.timeperframe.denominator);

	//初始化视频采集方式(mmap)
	if (init_mmap(args)) {
		printf("init_mmap failed!\n");
	}

	return 0;
}

//int start_capturing(struct globData *args) {
int start_capturing(int fd, int n_buffer) {
	unsigned int i;
	enum v4l2_buf_type type;

	//将申请的内核缓冲区放入一个队列中
	for (i = 0; i < n_buffer; i++) {
		struct v4l2_buffer buf;

		bzero(&buf, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;

		if (-1 == ioctl(fd, VIDIOC_QBUF, &buf)) {
			perror("Fail to ioctl 'VIDIOC_QBUF'");
			exit(EXIT_FAILURE);
		}
	}

	//开始采集数据
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)) {
		printf("i = %d.\n", i);
		perror("Fail to ioctl 'VIDIOC_STREAMON'");
		exit(EXIT_FAILURE);
	}

	return 0;
}

int read_frame(Camera* args, unsigned char *dest) {
	struct v4l2_buffer buf;
	bzero(&buf, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;

	if (-1 == ioctl(args->camera_fd, VIDIOC_DQBUF, &buf)) {
		switch (errno) {
		case EAGAIN:
//			printf("EAGAIN!\n");
			return 1;
		case EIO:
			/* Could ignore EIO, see spec. */
			/* fall through */
		default:
			//errno_exit("VIDIOC_DQBUF");
			perror("Fail to ioctl 'VIDIOC_DQBUF'");
			exit(EXIT_FAILURE);
		}
	}
	assert(buf.index < args->n_buffer);
	//读取进程空间的数据
	if (dest != NULL) {
//		printf("copy to dest memory!\n");
		memcpy(dest, (args->buf)[buf.index].start,
				(args->buf)[buf.index].length);
	}

	if (-1 == ioctl(args->camera_fd, VIDIOC_QBUF, &buf)) {
		perror("Fail to ioctl 'VIDIOC_QBUF'");
		exit(EXIT_FAILURE);
	}
//	printf("read ok!\n");

	return 0;
}

void stop_capturing(Camera* args) {
	enum v4l2_buf_type type;

	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (-1 == ioctl(args->camera_fd, VIDIOC_STREAMOFF, &type)) {
		perror("Fail to ioctl 'VIDIOC_STREAMOFF'\n");
		exit(EXIT_FAILURE);
	}	//stream off

	unsigned int i;

	for (i = 0; i < args->n_buffer; i++) {
		if (-1 == munmap(args->buf[i].start, args->buf[i].length)) {
			exit(EXIT_FAILURE);
		}
	}

	free(args->buf);

	if (-1 == close(args->camera_fd)) {
		perror("Fail to close fd");
		exit(EXIT_FAILURE);
	}

	free_camera(args);

	return;
}

int captureInit(Camera *args) {
	args->camera_fd = open_camer_device();

	init_camer_device(args);

	start_capturing(args->camera_fd, args->n_buffer);

	return 0;
}

int get_camera_data_size(Camera* args) {
	if (!args) {
		printf("can not get the camera data size, it null!\n");
		return -1;
	}
	if (!args->buf->start) {
		printf("can not get the camera buf data size, it null!\n");
		return -1;
	}

	return args->buf->length;
}

//int main() {
//	unsigned char test[320 * 240 * 3];
//	Camera *camera_s;
//
//	camera_s = calloc_camera();
//	camera_s->width = 320;
//	camera_s->height = 240;
//	camera_s->type = V4L2_PIX_FMT_MJPEG;
//
//	captureInit(camera_s);
//	sleep(1);
//	read_frame(camera_s, test);
//	write_data_to_file(test, (camera_s->buf)[0].length, "test.jpg");
//	stop_capturing(camera_s);
//	free_camera(camera_s);
//
//	return 0;
//}
